/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ytmc.downloader;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import ytmc.TimePeriod;
import ytmc.TransferSpeed;

/**
 *
 * @author K
 */
public class DownloadTaskRunner
{
    private final Object lock = new Object();

    private final DownloadTask task;
    private ArrayList<PropertyChangeListener> listeners;
    private Timer updater;
    private long totalDownloadSize;
    private long accumulator;
    private long downloadedSize;
    private boolean finished;

    public DownloadTask getTask()
    {
        return task;
    }

    public boolean isFinished()
    {
        synchronized (lock)
        {
            return finished;
        }
    }

    private void setFinished(boolean finished)
    {
        synchronized (lock)
        {
            boolean oldValue = this.finished;
            this.finished = finished;
            notifyPropertyChangeListeners(new PropertyChangeEvent(this, "finished", oldValue, finished));
        }
    }

    public DownloadTaskRunner(DownloadTask task)
    {
        this.task = task;
        this.listeners = new ArrayList<PropertyChangeListener>();
        this.updater = new Timer();
        this.downloadedSize = 0;
        this.accumulator = 0;
        this.totalDownloadSize = this.task.getSize().getSize();
    }

    public void addPropertyChangeListener(PropertyChangeListener listener)
    {
        synchronized (lock)
        {
            if (!listeners.contains(listener))
            {
                listeners.add(listener);
            }
        }
    }

    public void removePropertyChangeListener(PropertyChangeListener listener)
    {
        synchronized (lock)
        {
            if (listeners.contains(listener))
            {
                listeners.remove(listener);
            }
        }
    }

    private void notifyPropertyChangeListeners(PropertyChangeEvent event)
    {
        for (PropertyChangeListener propertyChangeListener : listeners)
        {
            propertyChangeListener.propertyChange(event);
        }
    }

    public void downloadAsync()
    {
        Thread worker = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    start();
                    updater.schedule(new UpdateTask(), 1000, 1000);
                    executeDownload(task.getDirectVideoURL());
                    updater.cancel();
                    successfulFinish();
                }
                catch (MalformedURLException ex)
                {
                    Logger.getLogger(DownloadTaskRunner.class.getName()).log(Level.SEVERE, null, ex);
                    failedFinish();
                }
                catch (FileNotFoundException ex)
                {
                    Logger.getLogger(DownloadTaskRunner.class.getName()).log(Level.SEVERE, null, ex);
                    failedFinish();
                }
                catch (IOException ex)
                {
                    Logger.getLogger(DownloadTaskRunner.class.getName()).log(Level.SEVERE, null, ex);
                    failedFinish();
                }
            }
        });
        worker.start();
    }

    private void executeDownload(String url) throws HttpResponseException, IOException, FileNotFoundException
    {
        HttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet(url);
        HttpResponse response = client.execute(request);
        
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK )
        {   
            updateDirectVideoURL();

            client.getConnectionManager().shutdown();
            client = new DefaultHttpClient();
            request = new HttpGet(task.getDirectVideoURL());
            response = client.execute(request);

            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
            {
                throw new HttpResponseException(response.getStatusLine().getStatusCode(), task.getDirectVideoURL());
            }
        }

        HttpEntity entity = response.getEntity();
        if (entity != null)
        {
            InputStream inputStream = null;
            OutputStream outputStream = null;

            try
            {
                outputStream = new FileOutputStream(new File(task.getSavePath()));
                inputStream = entity.getContent();

                int i;
                byte[] buffer = new byte[1024];
                while ((i = inputStream.read(buffer)) != -1)
                {
                    accumulator += i;
                    downloadedSize += i;
                    outputStream.write(buffer, 0, i);
                }
            }
            finally
            {
                if(inputStream != null)
                {
                    inputStream.close();
                }

                if(outputStream != null)
                {
                    outputStream.close();
                }
            }
        }
    }

    private void updateDirectVideoURL()
    {
        VideoDownloadLinkRetriver linkRetriver = new VideoDownloadLinkRetriver(task.getUrl());
        linkRetriver.retrieve();
        task.setDirectVideoURL(linkRetriver.getVideoDownloadURL());
    }

    private void start()
    {
        setFinished(false);
        downloadedSize = 0;
        accumulator = 0;
        task.setStatus(DownloadTaskStatus.RUNNING);
    }

    private void successfulFinish()
    {
        task.setProgress(100);
        task.setDownloadSpeed(new TransferSpeed(0));
        task.setRemainingTime(new TimePeriod(0));
        task.setStatus(DownloadTaskStatus.FINISHED);

        notifyPropertyChangeListeners(new PropertyChangeEvent(this, "task", task, task));

        setFinished(true);
    }

    private void failedFinish()
    {
        task.setDownloadSpeed(new TransferSpeed(0));
        task.setRemainingTime(new TimePeriod(0));
        task.setStatus(DownloadTaskStatus.FAILED);

        notifyPropertyChangeListeners(new PropertyChangeEvent(this, "task", task, task));

        setFinished(true);
    }

    private class UpdateTask extends TimerTask
    {
        @Override
        public void run()
        {
            int progress = (int)Math.ceil(((double)downloadedSize / (double)totalDownloadSize) * 100.0);
            task.setProgress(progress);
            task.setDownloadSpeed(new TransferSpeed(accumulator));
            task.setRemainingTime(new TimePeriod((int)Math.ceil((double)(totalDownloadSize - downloadedSize) / (double)accumulator)));
            accumulator = 0;

            notifyPropertyChangeListeners(new PropertyChangeEvent(this, "task", task, task));
        }
    }
}
